/*
SolutionSet JavaScript Library, requires Prototype version 1.6.0
Copyright (c) 2008, SolutionSet, All rights reserved.
Code licensed under the MIT License:
https://solutionset-javascript.googlecode.com/svn/trunk/docs/license.txt 		
*/

/**
 * Event Selectors
 *
 * @module eventselectors
 * @requires prototype, prototype_ss
 * @optional animation, connection, get
 * @namespace Solution.core
 * @title Event Selectors
 */

/**
 * EventSelectors allow you access to Javascript events using a CSS style syntax.
 * It goes one step beyond Javascript events to also give you :loaded, which allows
 * you to wait until an item is loaded in the document before you begin to interact
 * with it.
 *
 * @class EventSelectors
 * @constructor
 * @param
 */

Solution.namespace('Solution.util');

Solution.util.Task = {
	/**
	 * Specifies that a ruleset should be executed after the pages is 100% loaded
	 * @const
	 */
	PHASE_BINLOAD: 'binload',

	/**
	 * Specifies that a ruleset should be executed immediately after the DOM becomes available
	 * @const
	 */
	PHASE_DOMLOAD: 'domload',

	/**
	 * Experimental phase that causes a ruleset to be executed repeatedly until the page is 100% loaded
	 * @const
	 */
	//TODO: implement the now phase
	PHASE_NOW: 'now',

	/**
	 * @var Array
	 */
	rules: [],

	/**
	 * @var Array
	 */
	timers: [],

	/**
	 * Safety flag. Prevents BINLoad events from getting executed (due to this.apply()) before the window has truly loaded.
	 * @var bool
	 */
	windowLoaded: false,

	/**
	 * RegExp used to find pseudoClass events at the end of selectors. Stashed here so it's compiled only once.
	 * @var RegExp
	 */
	findPseudoClass: new RegExp('(.*):((event)\\("([0-9A-Za-z]*)"\\)|(loaded))$'),

	/**
	 * Register selector rule-sets
	 * Example:
	 * Solution.util.Task.register({
	 *   'a' : function(el) {
	 *      //arbitrary code
	 *      el.hide(); // hides every link on the page once the page loads
	 *   }
	 * });
	 * @param object ruleset
	 * @param const phase default this.PHASE_BINLOAD, must be this.PHASE_BINLOAD or this.PHASE_DOMLOAD
	 * @return void
	 */
	register: function(ruleset, phase) {
		if (typeof(phase) == 'undefined' || phase != this.PHASE_DOMLOAD)
			phase = this.PHASE_BINLOAD;
		for (var selector in ruleset) {
			switch (selector.toLowerCase()) {
				case 'window:binload':
					phase = this.PHASE_BINLOAD;
				break;
				case 'window:domload':
					phase = this.PHASE_DOMLOAD;
				break;
			}

			this.rules.push({
				selector: selector,
				func: ruleset[selector],
				phase: phase,
				applications: []
			});
		}
	},

	/**
	 * Re-applies all rules registered. This automatically runs after any successful Ajax method completes.
	 * @param void
	 * @return void
	 */
	apply: function() {
		this._fireEventPhase(this.PHASE_DOMLOAD);
		this._fireEventPhase(this.PHASE_BINLOAD);
	},

	/**
	 * Executes a given rule
	 * @private
	 * @param object rule
	 * @return void
	 */
	_executeRule: function(rule) {
		var selectors = $A(rule.selector.split(','));
		for (var x = 0, selectorsLength = selectors.length; x < selectorsLength; x++) {
			var selector = selectors[x];
			// Seek out custom pseudoClasses such as [element:event("click")] or [element:loaded]
			var pseudoClassData = this.findPseudoClass.exec(selector);
			// Scrape off the pseudoClass so it doesn't cause trouble for $$()
			if (pseudoClassData != null) selector = pseudoClassData[1];

			var elements = (selector == 'window') ? [window] : $$(selector);
			for (var y = 0, elementsLength = elements.length; y < elementsLength; y++) {
				var element = elements[y], event = false, isRedundant = false;

				// Check to see if this will be a redundant application
				for (var z = 0, applicationsLength = rule.applications.length; z < applicationsLength; z++) {
					// node has gone stale (no longer exists), application is pointless and impossible...
					if (rule.applications[z].node.parentNode == null && rule.applications[z].node != window) {
						// ...so remove it while we're here.
						rule.applications.splice(z, 1);
					// Node has already seen application of this function...
					} else if (rule.applications[z].node == element) {
						// Flag it
						isRedundant = true;
					}
				}

				if (isRedundant == false) {
					if (pseudoClassData == null) {
						//NOTE: we defer the execution of the rules to allow for a faster feel in the browser
						rule.func.defer(element, y);
					} else {
						switch (pseudoClassData[3]) {
							// special :loaded psuedo class
							case 'loaded':
								//TODO: what does this really do??
								//TODO: move this logic to the "now" phase. After binload all "now" rules should be reclassified as "binload".
								//NOTE: I don't believe this code really makes any sense here. What we'd really want to do is poll the DOM until the selector started returning elements and continue that until the window claimed to be loaded. After the window was fully loaded, :loaded selectors could be run immediately, ignoring the timeout altogether. This would allow for a PHASE_NOW although it would be stupid slow if it were abused.
								this.timers[selector] = setInterval(function(element, timer, rule) {
									var node = $(element);
									if(element.tagName != 'undefined') {
										clearInterval(this.timers[timer]);
										rule.func(node, y);
									}
								}.bind(this, element, selector, rule), 15);
							break;

							// special :event() psuedo class
							case 'event':
								var e = Event.observe(element, pseudoClassData[4], rule.func.bind(this, element));
							break;
						}
					}
					rule.applications.push({
						node: element,
						e: e
					});
				}
			}
		}
	},

	/**
	 * Executes rules related to a particular phase
	 * @private
	 * @param const phase must be this.PHASE_BINLOAD or this.PHASE_DOMLOAD
	 * @return void
	 */
	_fireEventPhase: function(phase) {
		if ((phase == this.PHASE_BINLOAD && this.windowLoaded) || phase != this.PHASE_BINLOAD) {
			for (var x = 0, ruleLength = this.rules.length; x < ruleLength; x++) {
				if (this.rules[x].phase == phase) this._executeRule(this.rules[x]);
			}
		}
	},

	/**
	 * @private
	 * @param void
	 * @return void
	 */
	_unloadEventCache: function() {
		//TODO: when is this ever called? should it be registered to fire on window.unload??
		var rules = this.rules
		for (var i = 0, ruleLength = rules.length; i < ruleLength; i++) {
			var applications = rules[i].applications;
			for (var x = 0, appLength = applications.length; x < appLength; x++) {
				Event.unObserve(applications[x].e);
			}
			this.rules[i].applications = [];
		}
	}
};

/**
 * Register the Binary load event. This fires when the page is 100% loaded.
 */
Event.observe(window, 'load', function() {
	this.windowLoaded = true;
	this._fireEventPhase(this.PHASE_BINLOAD);
}.bind(Solution.util.Task), false);

/**
 * Register the DOM load event. This fires when the HTML is fully loaded but before other files, such as images or flash has loaded. This will increase responsiveness when a page includes large amounts of images.
 */
Event.observe(document, 'dom:loaded', Solution.util.Task._fireEventPhase.bind(Solution.util.Task, Solution.util.Task.PHASE_DOMLOAD), false);

/**
 * Register the apply function to run at the completion of any AJjax request. This will ensure that any new elements added to the DOM are aduquately handled.
 */
Ajax.Responders.register({
	onComplete: function() {Solution.util.Task.apply();}
});
